feat(core): add endpoint for creating inbound webhook connector#58
feat(core): add endpoint for creating inbound webhook connector#58foukou19 wants to merge 4 commits into
Conversation
️✅ There are no secrets present in this pull request anymore.If these secrets were true positive and are still valid, we highly recommend you to revoke them. 🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request. |
|
|
|
|
cb49b68 to
6e81bd7
Compare
| * @param securityType the security type to check (e.g., "BASIC_AUTH", "HMAC_SHA256") | ||
| * @return true if this strategy handles this security type | ||
| */ | ||
| boolean supports(String securityType); |
There was a problem hiding this comment.
validate security type using enum
There was a problem hiding this comment.
Pull request overview
This PR introduces inbound webhook connectors in IDP-Core: a management API to CRUD connector configurations (identifier/title, security strategy, dynamic mappings) and a generic public webhook ingestion endpoint that authenticates requests based on the stored connector security settings.
Changes:
- Add webhook connector persistence (Flyway migrations, JPA entities/repositories, Postgres adapter) and domain models/services for CRUD + validation.
- Add runtime webhook ingestion endpoint (
POST /webhooks/{configurationId}) with strategy-based security validation (HMAC, static token, basic auth, JWT bearer, none). - Add JSLT-based mapping validation plus docs and test coverage for the new behavior.
Reviewed changes
Copilot reviewed 87 out of 91 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test/resources/integration_test/json/webhook/v1/putWebhook_409_title_already_exists.json | Test payload for update conflict (title) |
| src/test/resources/integration_test/json/webhook/v1/putWebhook_200.json | Test payload for successful update |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_409_identifier_already_exists.json | Test payload for create conflict (identifier) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_400_mappings_empty.json | Test payload for create validation (empty mappings) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_400_invalid_security_type.json | Test payload for create validation (invalid security type) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_400_invalid_jslt.json | Test payload for create validation (invalid JSLT) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_400_identifier_missing.json | Test payload for create validation (missing identifier) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_400_identifier_blank.json | Test payload for create validation (blank identifier) |
| src/test/resources/integration_test/json/webhook/v1/postWebhook_201.json | Test payload for successful create |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/WebhookSecurityValidatorDispatcherTest.java | Unit tests for runtime strategy dispatch |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/StaticTokenSecurityValidatorTest.java | Unit tests for static token runtime validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/JwtBearerSecurityValidatorTest.java | Unit tests for JWT bearer runtime validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/HmacSignatureValidatorTest.java | Unit tests for HMAC digest helper |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/creation/StaticTokenWebhookSecurityCreationValidatorTest.java | Unit tests for static token config validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/creation/JwtBearerWebhookSecurityCreationValidatorTest.java | Unit tests for JWT bearer config validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/creation/HmacSha256WebhookSecurityCreationValidatorTest.java | Unit tests for HMAC config validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/creation/BasicAuthWebhookSecurityCreationValidatorTest.java | Unit tests for basic auth config validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/BasicAuthSecurityValidatorTest.java | Unit tests for basic auth runtime validation |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/webhook/InboundWebhookMapperTest.java | Unit tests for API ↔ domain mapping |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/handler/ApiExceptionHandlerTest.java | Adds coverage for new webhook/mapping exceptions |
| src/test/java/com/decathlon/idp_core/infrastructure/adapters/api/controller/InboundWebhookManagementControllerTest.java | Integration tests for webhook connector management API |
| src/test/java/com/decathlon/idp_core/domain/service/webhook/WebhookConnectorValidationServiceTest.java | Unit tests for connector validation logic |
| src/test/java/com/decathlon/idp_core/domain/service/webhook/WebhookConnectorServiceTest.java | Unit tests for connector CRUD orchestration |
| src/test/java/com/decathlon/idp_core/domain/service/webhook/security/WebhookSecurityValidationServiceTest.java | Unit tests for security config validation service |
| src/test/java/com/decathlon/idp_core/domain/service/webhook/EntityDynamicMappingValidationServiceTest.java | Unit tests for mapping ↔ template validation |
| src/main/resources/db/migration/V4_2__create_webhook_template_mapping_table.sql | Flyway migration for connector↔template mapping table |
| src/main/resources/db/migration/V4_1__create_webhook_connector_table.sql | Flyway migration for webhook connector table |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/service/InboundWebhookHandler.java | Runtime handler: resolve connector + validate security |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/WebhookSecurityValidatorDispatcher.java | Runtime strategy dispatcher for security validators |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/WebhookSecurityConfigurationUtils.java | Shared utilities for config lookup/secret resolution |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/WebhookJwtDecoderProvider.java | Cached JWT decoder provider keyed by JWKS URI |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/StaticTokenSecurityValidator.java | Static token security strategy implementation |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/JwtBearerSecurityValidator.java | JWT bearer security strategy implementation |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/HmacSignatureValidator.java | HMAC digest helper used by HMAC strategy |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/HmacSha256SecurityValidator.java | HMAC SHA-256 security strategy implementation |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/security/BasicAuthSecurityValidator.java | Basic auth security strategy implementation |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/model/StaticTokenConfig.java | Polymorphic security config model (static token) |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/model/SecurityConfig.java | Polymorphic security config interface |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/model/JwtBearerConfig.java | Polymorphic security config model (JWT bearer) |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/model/HmacConfig.java | Polymorphic security config model (HMAC) |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/model/BasicAuthConfig.java | Polymorphic security config model (basic auth) |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/webhook/controller/InboundWebhookController.java | Public webhook ingestion endpoint |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaWebhookTemplateMappingRepository.java | JPA repository for mapping table |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/repository/JpaWebhookConnectorRepository.java | JPA repository for connector table |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/PostgresWebhookConnectorAdapter.java | Implements connector repository port + mapping persistence |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/webhook/WebhookTemplateMappingJpaEntity.java | JPA entity for mapping table |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/model/webhook/WebhookConnectorJpaEntity.java | JPA entity for connector table (JSONB columns) |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/mapper/WebhookConnectorPersistenceMapper.java | MapStruct mapping: domain ↔ JPA |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/mapper/common/WebhookConnectorJsonbHelper.java | JSONB serialization/deserialization helper |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/persistence/configuration/JpaAuditingConfiguration.java | Enables JPA auditing for created/updated timestamps |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/entity_mapping/jslt/JsltEntityMappingValidator.java | Infrastructure validator for JSLT expressions |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/mapper/webhook/InboundWebhookMapper.java | API DTO ↔ domain mapping for inbound webhooks |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/handler/ApiExceptionHandler.java | Adds exception→HTTP mappings for webhook features |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/webhook/InboundWebhookSecurityDtoOut.java | Outbound DTO for security type |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/webhook/InboundWebhookMappingDtoOut.java | Outbound DTO for mapping rule |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/webhook/InboundWebhookEntityMappingDtoOut.java | Outbound DTO for entity mapping |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/out/webhook/InboundWebhookDtoOut.java | Outbound DTO for connector |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/in/InboundWebhookSecurityContractDtoIn.java | Inbound DTO for {type, config} security contract |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/in/InboundWebhookMappingDtoIn.java | Inbound DTO for mapping rule |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/in/InboundWebhookEntityMappingDtoIn.java | Inbound DTO for entity mapping |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/dto/in/InboundWebhookCreateDtoIn.java | Inbound DTO for connector create/update |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/controller/InboundWebhookManagementController.java | CRUD + listing API for connectors |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/configuration/SwaggerDescription.java | Adds OpenAPI description constants |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/configuration/SwaggerConfiguration.java | Adds Page schema for connector listing |
| src/main/java/com/decathlon/idp_core/infrastructure/adapters/api/configuration/SecurityConfiguration.java | Permits /webhooks/** and disables CSRF there |
| src/main/java/com/decathlon/idp_core/domain/service/webhook/WebhookConnectorValidationService.java | Domain validation: uniqueness + mapping + security |
| src/main/java/com/decathlon/idp_core/domain/service/webhook/WebhookConnectorService.java | Domain service: connector CRUD orchestration |
| src/main/java/com/decathlon/idp_core/domain/service/webhook/security/WebhookSecurityValidationService.java | Domain service: validate security configs |
| src/main/java/com/decathlon/idp_core/domain/service/webhook/EntityDynamicMappingValidationService.java | Domain validation: mapping keys vs template + required fields |
| src/main/java/com/decathlon/idp_core/domain/service/entity_template/EntityTemplateValidationService.java | Adds template property/relation lookup validation helpers |
| src/main/java/com/decathlon/idp_core/domain/port/WebhookSecurityStrategy.java | Port contract for security strategies |
| src/main/java/com/decathlon/idp_core/domain/port/WebhookConnectorRepositoryPort.java | Port contract for connector persistence |
| src/main/java/com/decathlon/idp_core/domain/port/EntityDynamicMapperValidator.java | Port contract for mapping DSL validation |
| src/main/java/com/decathlon/idp_core/domain/model/webhook/WebhookSecurity.java | Domain model for security type + config |
| src/main/java/com/decathlon/idp_core/domain/model/webhook/WebhookConnector.java | Domain aggregate for connector configuration |
| src/main/java/com/decathlon/idp_core/domain/model/enums/WebhookSecurityType.java | Enum of supported security strategies |
| src/main/java/com/decathlon/idp_core/domain/model/entity_mapping/EntityDynamicMapping.java | Domain model for mapping rule |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookTemplateHasNoPropertiesException.java | Domain exception for invalid mapping vs template |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookSecurityConfigurationException.java | Domain exception for invalid security configuration |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookConnectorTitleAlreadyExistsException.java | Domain exception for title conflict |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookConnectorNotFoundException.java | Domain exception for missing connector |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookConnectorAlreadyExistException.java | Domain exception for identifier conflict |
| src/main/java/com/decathlon/idp_core/domain/exception/webhook/WebhookAuthenticationException.java | Domain exception for runtime auth failures |
| src/main/java/com/decathlon/idp_core/domain/exception/entity_template/RelationNameNotFoundEntityTemplateRelationsException.java | Domain exception for invalid relation name |
| src/main/java/com/decathlon/idp_core/domain/exception/entity_template/PropertyNameNotFoundEntityTemplatePropertiesException.java | Domain exception for invalid property name |
| src/main/java/com/decathlon/idp_core/domain/exception/entity_mapping/EntityDynamicMappingConfigurationException.java | Domain exception for invalid mapping DSL |
| src/main/java/com/decathlon/idp_core/domain/constant/ValidationMessages.java | Adds webhook validation message constants |
| pom.xml | Adds JSLT dependency |
| docs/zensical.toml | Adds webhooks page to docs navigation |
| docs/src/concepts/webhooks.md | New docs page describing connectors, mappings, security |
| docs/src/concepts/index.md | Links webhooks concept from the concepts index |
| import com.decathlon.idp_core.domain.model.enums.WebhookSecurityType; | ||
| import com.decathlon.idp_core.domain.model.webhook.WebhookConnector; | ||
| import com.decathlon.idp_core.infrastructure.adapters.api.dto.in.InboundWebhookCreateDtoIn;import com.decathlon.idp_core.infrastructure.adapters.api.dto.in.InboundWebhookEntityMappingDtoIn; | ||
| import com.decathlon.idp_core.infrastructure.adapters.api.dto.in.InboundWebhookMappingDtoIn; | ||
| import com.decathlon.idp_core.infrastructure.adapters.api.dto.in.InboundWebhookSecurityContractDtoIn; |
| try { | ||
| Mac mac = Mac.getInstance("HmacSHA256"); | ||
| mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); | ||
| byte[] digest = mac.doFinal(payload); | ||
| return toHex(digest); | ||
| } catch (Exception exception) { | ||
| throw new WebhookAuthenticationException("Unable to compute HMAC signature"); | ||
| } |
| String secret = WebhookSecurityConfigurationUtils.getSecretFromEnvironment(alias); | ||
|
|
||
| String expected = prefix + signatureValidator.computeHexSha256(rawBody, secret); | ||
| if (!expected.equals(provided)) { | ||
| throw new WebhookAuthenticationException("Invalid HMAC signature"); | ||
| } |
| String expected = WebhookSecurityConfigurationUtils.getSecretFromEnvironment(alias); | ||
|
|
||
| if (!expected.equals(provided)) { | ||
| throw new WebhookAuthenticationException("Invalid static token"); | ||
| } |
| String expectedRaw = username + ":" + password; | ||
| String expected = "Basic " + Base64.getEncoder().encodeToString(expectedRaw.getBytes(StandardCharsets.UTF_8)); | ||
| if (!expected.equals(authorization)) { | ||
| throw new WebhookAuthenticationException("Invalid basic authentication credentials"); | ||
| } |
| private InboundWebhookMappingDtoOut fromEntityMappingToDto(EntityDynamicMapping mapping) { | ||
| return new InboundWebhookMappingDtoOut( | ||
| mapping.templateIdentifier(), | ||
| mapping.filter(), | ||
| new InboundWebhookEntityMappingDtoOut( | ||
| mapping.entityIdentifier(), | ||
| mapping.entityTitle(), | ||
| Map.copyOf(mapping.properties()), | ||
| Map.copyOf(mapping.relations()) | ||
| ) | ||
| ); |
| if (webhookMappingRelations == null || webhookMappingRelations.isEmpty()) { | ||
| return; | ||
| } | ||
| webhookMappingRelations.keySet().forEach(relationName -> entityTemplateValidationService.validateRelationNameAlreadyExistInTemplate(entityTemplate.relationsDefinitions(), relationName)); |
There was a problem hiding this comment.
I would suggets to have this method in this class intead of creating a method in the entityTemplateValidation. We already have the necessary information for validating here.
| ); | ||
| } | ||
|
|
||
| private void validateRelationNameAlreadyExistInTemplate(Map<String, String> webhookMappingRelations, EntityTemplate entityTemplate) { |
There was a problem hiding this comment.
| private void validateRelationNameAlreadyExistInTemplate(Map<String, String> webhookMappingRelations, EntityTemplate entityTemplate) { | |
| private void validateRelationsExistsInTemplate(Map<String, String> webhookMappingRelations, EntityTemplate entityTemplate) { | |
| if (webhookMappingRelations == null || webhookMappingRelations.isEmpty()) { | |
| return; | |
| } | |
| List<String> unknownRelations = webhookMappingRelations.keySet().stream() | |
| .filter(relationName -> entityTemplate.relationsDefinitions().stream() | |
| .noneMatch(rd -> rd.name().equals(relationName))) | |
| .toList(); | |
| if (!unknownRelations.isEmpty()) { | |
| throw new WebhookTemplateHasNoPropertiesException( | |
| String.format("The mapping references unknown relations: %s", String.join(", ", unknownRelations)) | |
| ); | |
| } | |
| } |
| @Slf4j | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class InboundWebhookHandler { |
There was a problem hiding this comment.
This componet will be implemented in the Generic Camel Route. Integration to discuss.
af478c6 to
6e81bd7
Compare
0e79bea to
a840e86
Compare
Code Coverage OverviewLanguages: Java Java / code-coverage/jacocoThe overall coverage in the branch is 86%. Coverage data for the branch is not yet available. Show a code coverage summary of the most covered files.
Updated |
|
fb72b72 to
a264db8
Compare
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
a264db8 to
c400ac6
Compare
| @Table(name = "webhook_template_mapping") | ||
| @Getter | ||
| @Setter | ||
| @Builder |
8274fae to
7089d04
Compare
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
7089d04 to
dc7d4fb
Compare
| "title": "Blank Identifier", | ||
| "description": "Should fail", | ||
| "enabled": true, | ||
| "mapping_identifiers": ["microservice-mapping"], |
| id UUID PRIMARY KEY, | ||
| identifier VARCHAR(255) UNIQUE NOT NULL, | ||
| template_identifier VARCHAR(255) NOT NULL, | ||
| filter VARCHAR(255) NOT NULL, | ||
| entity_identifier VARCHAR(255) NOT NULL, | ||
| entity_title VARCHAR(255) NOT NULL, | ||
| properties JSONB NOT NULL DEFAULT '{}'::jsonb, | ||
| relations JSONB NOT NULL DEFAULT '{}'::jsonb |
| CREATE INDEX idx_webhook_template_mapping_webhook ON webhook_template_mapping (webhook_id); | ||
|
|
||
| CREATE INDEX idx_webhook_template_mapping_template ON webhook_template_mapping (template_id); | ||
|
|
||
| CREATE INDEX idx_webhook_template_mapping_entity_mapping ON webhook_template_mapping (entity_mapping_id); |
| @Mapping(target = "relations", qualifiedByName = "jsonStringToMap") | ||
| EntityDynamicMapping toDomain(EntityDynamicMappingJpaEntity jpa); | ||
|
|
||
| @Mapping(target = "id", ignore = true) |
| @OneToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "entity_mapping_id", nullable = false, insertable = false, updatable = false) | ||
| private EntityDynamicMappingJpaEntity entityMapping; |
Signed-off-by: foukou19 <ferial.oukoukas@decathlon.com>
591d123 to
a276d46
Compare
|





feat(api): add CRUD and validation for webhook configurations
What this PR Provides
This PR introduces the API endpoints for managing (CRUD) inbound webhook configurations.
It establishes the foundation for receiving data from external systems by allowing users to define how incoming payloads should be mapped to entities.
The core logic of this PR includes:
POST /api/v1/inbound-webhooks: To create a new webhook configuration.GET /api/v1/inbound-webhooks: To list all existing configurations.GET /api/v1/inbound-webhooks/{identifier}: To retrieve a specific configuration.PUT /api/v1/inbound-webhooks/{identifier}: To update an existing configuration.DELETE /api/v1/inbound-webhooks/{identifier}: To delete a configuration.POST) or update (PUT), the backend now validates the syntax of all JSLT expressions provided in themappingsarray. This prevents saving configurations with invalid transformation logic.securityobject with specific types. The supported types are:HMAC_SHA256: For signature validation using a shared secret (e.g., GitHub, Sonar).STATIC_TOKEN: For simple authentication using a static token in a header.Review
The reviewer must double-check these points:
How to test
1. Initial State
2. What and how to test
A. Create a Valid Webhook Configuration
POSTrequest to/api/v1/inbound-webhookswith the following JSON body:{ "identifier": "sonar_webhook", "title": "Sonar Webhook", "enabled": true, "mappings": [ { "template": "sonar_project", "filter": ".visibility == \"private\"", "entity": { "identifier": ".key | tostring", "title": ".name", "properties": { "project_name": ".name | tostring" } } } ], "security": { "type": "HMAC_SHA256", "config": { "header_name": "X-Sonar-Webhook-HMAC-SHA256", "secret_alias": "SONAR_WEBHOOK_SECRET" } } }201 Createdstatus, and the response body contains the created configuration.B. Attempt to Create a Configuration with Invalid JSLT
POSTrequest to/api/v1/inbound-webhookswith a malformed JSLT expression (e.g., a missing quote):{ "identifier": "invalid_jslt_webhook", "title": "Invalid JSLT", "mappings": [ { "template": "test", "entity": { "identifier": ".key | tostring(" } } ] }400 Bad Requeststatus, and the error message indicates a JSLT syntax validation error.C. Attempt to Create a Configuration with an Invalid Security Type
POSTrequest to/api/v1/inbound-webhookswith an unsupported security type:{ "identifier": "invalid_security_webhook", "title": "Invalid Security", "mappings": [ { "template": "test", "entity": { "identifier": ".key" } } ], "security": { "type": "INVALID_TYPE" } }400 Bad Requeststatus, and the error message indicates that the security type is not supported.D. Read, Update, and Delete the Configuration
GETrequest to/api/v1/inbound-webhooks/sonar_webhook.200 OKstatus and returns the full configuration forsonar_webhook.PUTrequest to/api/v1/inbound-webhooks/sonar_webhookwith"enabled": false.200 OKstatus. A subsequentGETrequest should show"enabled": false.DELETErequest to/api/v1/inbound-webhooks/sonar_webhook.204 No Contentstatus.GETrequest to/api/v1/inbound-webhooks/sonar_webhook.404 Not Foundstatus.Breaking changes (if any)